﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ServiceModel.DomainServices.Client;
using System.Windows.Threading;
using System.Windows;

namespace SL.MVVMS.RIA
{
    /// <summary>
    /// Manages a DomainContext including Load and Save operations
    /// Calls all Actions on the UI Main Thread
    /// </summary>
    /// 
    [Obsolete("ContextManager functions are now integrated with the ViewModel Base Class")]
    public class ContextManager
    {
        /// <summary>
        /// Context to Manage
        /// </summary>
        DomainContext _context;

        internal DomainContext ServerContext
        {
            get
            {
                return _context;
            }
        }

        /// <summary>
        /// Action to Take when Errors Occur
        /// </summary>
        Action<Exception> _onError;

        /// <summary>
        /// List of Operations that might be working still
        /// </summary>
        List<OperationBase> _possiblyPendingOperations;

        /// <summary>
        /// Object to Control a Batch of Operations
        /// </summary>
        DomainContextLoadBatch _loadingBatch;

        /// <summary>
        /// List of active Batches
        /// </summary>
        List<DomainContextLoadBatch> _batches;

        /// <summary>
        /// Manages Operations for a Context, including Batching and Deleting Functions
        /// </summary>
        /// <param name="context">Domain Context to Wrapper</param>
        /// <param name="onError">What to do when Errors Occur</param>
        public ContextManager(DomainContext context, Action<Exception> onError)
        {
            _context = context;
            _onError = onError;
            _possiblyPendingOperations = new List<OperationBase>();
            _batches = new List<DomainContextLoadBatch>();
        }

        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <returns>LoadOperation which is Executing</returns>
        public LoadOperation LoadData(EntityQuery Query)
        {
            return LoadData(Query, LoadBehavior.KeepCurrent, null, false, null);
        }

        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <param name="ProcessDeletes">Should this query cause similar items to be deleted</param>
        /// <returns>LoadOperation which is Executing</returns>
        public LoadOperation LoadData(EntityQuery Query, bool ProcessDeletes)
        {
            return LoadData(Query, LoadBehavior.KeepCurrent, null, ProcessDeletes, null);
        }

        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <param name="OnLoadComplete">Action to perform when done, only if not in a Batch</param>
        /// <returns>LoadOperation which is Executing</returns>
        public LoadOperation LoadData(EntityQuery Query, Action OnLoadComplete)
        {
            return LoadData(Query, LoadBehavior.KeepCurrent, OnLoadComplete, false, null);
        }


        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <param name="OnLoadComplete">Action to perform when done, only if not in a Batch</param>
        /// <param name="ProcessDeletes">Should this query cause similar items to be deleted</param>
        /// <returns>LoadOperation which is Executing</returns>

        public LoadOperation LoadData(EntityQuery Query, Action OnLoadComplete, bool ProcessDeletes)
        {
            return LoadData(Query, LoadBehavior.KeepCurrent, OnLoadComplete, ProcessDeletes, null);
        }

        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <param name="Behavior">Specified LoadBehavior, default = KeepCurrent</param>
        /// <param name="OnLoadComplete">Action to perform when done, only if not in a Batch</param>
        /// <param name="ProcessDeletes">Should this query cause similar items to be deleted</param>
        /// <returns>LoadOperation which is Executing</returns>
        public LoadOperation LoadData(EntityQuery Query, LoadBehavior Behavior, Action OnLoadComplete, bool ProcessDeletes)
        {
            return LoadData(Query, Behavior, OnLoadComplete, ProcessDeletes, null);
        }

        /// <summary>
        /// Loads Data using the Domain Context
        /// </summary>
        /// <param name="Query">Query to Execute</param>
        /// <param name="Behavior">Specified LoadBehavior, default = KeepCurrent</param>
        /// <param name="OnLoadComplete">Action to perform when done, only if not in a Batch</param>
        /// <param name="ProcessDeletes">Should this query cause similar items to be deleted</param>
        /// <param name="DeleteFilter">Function to filter items which will be removed.  If empty, and processDeletes=true, then all items not returned are deleted</param>
        /// <returns>LoadOperation which is Executing</returns>
        public LoadOperation LoadData(EntityQuery Query, LoadBehavior Behavior, Action OnLoadComplete, bool ProcessDeletes, Func<DeleteEventArgs, bool> DeleteFilter)
        {
            // This is the equivalent of Clear
            if (ProcessDeletes == true &&
                DeleteFilter == null)
                _context.EntityContainer.GetEntitySet(Query.EntityType).Clear();

            LoadOperation loadOp = _context.Load(Query, Behavior, (lo) =>
                {
                    // Remove the Operation from any Lists of Operations
                    _possiblyPendingOperations.Remove(lo);

                    // Check for Errors
                    if (lo.HasError)
                    {
                        if (lo.UserState == null)
                        {
                            if (_onError != null)
                                CallOnUI(() => { _onError(lo.Error); });
                            lo.MarkErrorAsHandled();
                        }
                    }
                    else
                    {
                        // If we want to remove Deleted Items, 
                        // and provided a Filter, do this now
                        if (ProcessDeletes == true &&
                            DeleteFilter != null)
                        {
                            EntitySet es = _context.EntityContainer.EntitySets.Where(e => e.EntityType == lo.EntityQuery.EntityType).Single();
                            var oldItems = (from e in es.Cast<Entity>()
                                           where lo.Entities.Contains(e) == false
                                           select e).ToList();
                            foreach (var oldItem in oldItems)
                            {
                                // Filter the Item if a Filter was provided
                                if (DeleteFilter != null)
                                {
                                    if (DeleteFilter(new DeleteEventArgs(oldItem)))
                                        es.Detach(oldItem);
                                }
                            }
                        }

                        // If part of a batch do not process
                        if (lo.UserState == null)
                        {
                            if (OnLoadComplete != null)
                                CallOnUI(OnLoadComplete);
                        }
                    }

                }, (_loadingBatch != null) ? _loadingBatch : null );

            // Log the operation as needed
            if (_loadingBatch != null)
                _loadingBatch.Add(loadOp);
            _possiblyPendingOperations.Add(loadOp);
            return loadOp;
        }

        /// <summary>
        /// Saves any added/deleted/updated data to the
        /// Managed Domain Context
        /// </summary>
        /// <param name="OnSaveComplete"></param>
        /// <returns></returns>
        public SubmitOperation SaveData(Action OnSaveComplete)
        {
            SubmitOperation saveOp = _context.SubmitChanges((so) =>
                {
                    // Remove the Operation from any Lists of Operations
                    _possiblyPendingOperations.Remove(so);

                    // Check for Errors
                    if (so.HasError)
                    {
                        if (so.UserState == null)
                        {
                            if (_onError != null)
                                CallOnUI(() => { _onError(so.Error); });
                            so.MarkErrorAsHandled();
                        }
                    }
                    else
                    {
                        // If part of a batch do not process
                        if (so.UserState == null)
                        {
                            if (OnSaveComplete != null)
                                CallOnUI(() => { OnSaveComplete(); });
                        }
                    }
                }, (_loadingBatch != null) ? _loadingBatch : null);

            // Log the operation as needed
            if (_loadingBatch != null)
                _loadingBatch.Add(saveOp);
            _possiblyPendingOperations.Add(saveOp);
            return saveOp;
        }

        /// <summary>
        /// Processes an InvokeOperation without a Callback
        /// </summary>
        /// <param name="Operation">Query to Execute</param>
        /// <param name="OnCallComplete">Action to perform when done, only if not in a Batch</param>
        public void InvokeCall(InvokeOperation Operation)
        {
            InvokeCall(Operation, null);
        }

        /// <summary>
        /// Processes an InvokeOperation with a Callback on UI Thread
        /// </summary>
        /// <param name="Operation">Query to Execute</param>
        /// <param name="OnCallComplete">Action to perform when done, only if not in a Batch</param>
        public void InvokeCall(InvokeOperation Operation, Action OnCallComplete)
        {
            Operation.Completed += (s, ea) =>
                {
                    // Remove the Operation from any Lists of Operations
                    _possiblyPendingOperations.Remove(Operation);

                    // Check for Errors
                    if (Operation.HasError)
                    {
                        if (Operation.UserState == null)
                        {
                            if (_onError != null)
                                CallOnUI(() => { _onError(Operation.Error); });
                            Operation.MarkErrorAsHandled();
                        }
                    }
                    else
                    {
                        // If part of a batch do not process
                        if (Operation.UserState == null)
                        {
                            if (OnCallComplete != null)
                                CallOnUI(OnCallComplete);
                        }
                    }

                };
            if (_loadingBatch != null)
                _loadingBatch.Add(Operation);
            _possiblyPendingOperations.Add(Operation);
        }


        /// <summary>
        /// Triggers LoadData and SaveData functions to queue to a batch
        /// and no actions are returned until this batch is completed
        /// before the BatchesLoaded() function is called.
        /// </summary>
        /// <param name="OnBatchComplete"></param>
        public void StartBatch(Action OnBatchComplete)
        {
            _loadingBatch = new DomainContextLoadBatch((batch) =>
            {
                _batches.Remove(batch);
                if (batch.FailedOperationCount > 0)
                {
                    string errMsg = "The following failures occurred: ";
                    foreach (OperationBase ob in batch.FailedOperations)
                    {
                        errMsg += ob.Error.Message + ", ";
                        ob.MarkErrorAsHandled();
                    }
                    CallOnUI(() => { _onError(new Exception(errMsg)); });
                }
                else
                {
                    CallOnUI(OnBatchComplete);
                }
            });
            _batches.Add(_loadingBatch);
        }

        /// <summary>
        /// Signifies batching is complete, and to resume
        /// normal Loading and Saving
        /// </summary>
        public void EndBatch()
        {
            _loadingBatch.CanComplete = true;
            _loadingBatch = null;
        }


        /// <summary>
        /// helper function to send action to UI
        /// </summary>
        /// <param name="action"></param>
        static void CallOnUI(Action action)
        {
            UIDispatcher.CallOnUI(action);
        }
    }
}
